from werkzeug.security import generate_password_hash, check_password_hash
from authlib.jose import jwt
from flask import current_app
from flask_login import UserMixin, AnonymousUserMixin
from datetime import datetime
from markdown import markdown
import bleach
from app import db
from . import login_manager

class Permission: 
    FOLLOW = 1 
    COMMENT = 2 
    WRITE = 4 
    MODERATE = 8 
    ADMIN = 16

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    default = db.Column(db.Boolean, default=False, index=True) 
    permissions = db.Column(db.Integer)
    users = db.relationship('User', backref='role', lazy='dynamic')

    def __init__(self, **kwargs): 
        super(Role, self).__init__(**kwargs) 
        if self.permissions is None: 
            self.permissions = 0

    def add_permission(self, perm): 
        if not self.has_permission(perm): 
            self.permissions += perm

    def remove_permission(self, perm): 
        if self.has_permission(perm): 
            self.permissions -= perm 
 
    def reset_permissions(self): 
        self.permissions = 0 
 
    def has_permission(self, perm): 
        return self.permissions & perm == perm
    
    def __repr__(self):
        return f'<Role {self.name}>'

    @staticmethod 
    def insert_roles(): 
        roles = { 
            'User': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE], 
            'Moderator': [Permission.FOLLOW, Permission.COMMENT, 
                Permission.WRITE, Permission.MODERATE], 
            'Administrator': [Permission.FOLLOW, Permission.COMMENT, 
                            Permission.WRITE, Permission.MODERATE, 
                            Permission.ADMIN], 
        } 
        default_role = 'User' 
        for r in roles: 
            role = Role.query.filter_by(name=r).first() 
            if role is None: 
                role = Role(name=r) 
            role.reset_permissions() 
            for perm in roles[r]: 
                role.add_permission(perm) 
            role.default = (role.name == default_role) 
            db.session.add(role) 
        db.session.commit()

class User(db.Model, UserMixin):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True) 
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))
    confirmed = db.Column(db.Boolean, default=False)
    name = db.Column(db.String(64)) 
    location = db.Column(db.String(64)) 
    about_me = db.Column(db.Text()) 
    member_since = db.Column(db.DateTime(), default=datetime.utcnow) 
    last_seen = db.Column(db.DateTime(), default=datetime.utcnow)

    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    posts = db.relationship('Post', backref='author', lazy='dynamic')

    def __init__(self, **kwargs): 
        super(User, self).__init__(**kwargs) 
        if self.role is None: 
            if self.email == current_app.config['FLASKY管理员']: 
                self.role = Role.query.filter_by(name='Administrator').first() 
            if self.role is None: 
                self.role = Role.query.filter_by(default=True).first() 
    
    @property
    def password(self):
        raise AttributeError('密码不是可读属性！')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

    def generate_confirmation_token(self, expiration=3600):
        header = {'alg': 'HS256'}
        key = current_app.config['SECRET_KEY']
        data = { 'confirm': self.id }
        return jwt.encode(header=header, payload=data, key=key)

    def confirm(self, token):
        key = current_app.config['SECRET_KEY']

        try:
            data = jwt.decode(token, key)
        except:
            return False
        if data.get('confirm') != self.id:
            return False
        self.confirmed = True
        db.session.add(self)
        return True

    def can(self, perm): 
        return self.role is not None and self.role.has_permission(perm) 
 
    def is_administrator(self): 
        return self.can(Permission.ADMIN)

    def ping(self): 
        self.last_seen = datetime.utcnow() 
        db.session.add(self) 
        db.session.commit()

    def __repr__(self):
        return f'<User {self.username}>'

class Post(db.Model): 
    __tablename__ = 'posts' 
    id = db.Column(db.Integer, primary_key=True) 
    body = db.Column(db.Text)
    body_html = db.Column(db.Text) 
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) 
    author_id = db.Column(db.Integer, db.ForeignKey('users.id'))

    @staticmethod 
    def on_changed_body(target, value, oldvalue, initiator): 
        allowed_tags = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 
                        'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 
                        'h1', 'h2', 'h3', 'p'] 
        target.body_html = bleach.linkify(bleach.clean( 
            markdown(value, output_format='html'), 
            tags=allowed_tags, strip=True))

db.event.listen(Post.body, 'set', Post.on_changed_body)


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

class AnonymousUser(AnonymousUserMixin): 
    def can(self, permissions): 
        return False 
 
    def is_administrator(self): 
        return False 
 
login_manager.anonymous_user = AnonymousUser